#include <GL\glew.h>
#include "GL\freeglut.h"
#include "ShaderUtil.h"
#include "SWRenderer.h"
#include "MatUtil.h"

using std::invalid_argument;
using std::min;
using std::max;
using std::abs;

#define EPSILON (1e-6)
#define INFINITY (1e6)

SWRenderer::SWRenderer(int width, int height) :
	_initialWidth(width), _initialHeight(height)
{
	CvarSystem& cvars = CvarSystem::getInstance();

	_drawType = cvars.addCvar("r_draw_type", FILL);
	_shadeType = cvars.addCvar("r_shade_type", FLAT);
	_wireframeColor = cvars.addCvar("r_wireframe_color", WHITE);
	_width = cvars.addCvar("r_width", width);
	_height = cvars.addCvar("r_height", height);
	_useGlut = cvars.addCvar("r_use_glut", false);
	
	_clip = cvars.addCvar("r_clip", true);

	initOpenGL();
	createBuffers();
}

void SWRenderer::initOpenGL()
{
	::glGenTextures(1, &glScreenTex);
	::glGenVertexArrays(1, &glScreenVtc);	
	::glBindVertexArray(glScreenVtc);

	GLuint buffer;
	::glGenBuffers(1, &buffer);

	const float vtc[]= { -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1 };
	const float tex[]= { 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1 };

	::glBindBuffer(GL_ARRAY_BUFFER, buffer);
	::glBufferData(GL_ARRAY_BUFFER, sizeof(vtc) + sizeof(tex), NULL, GL_STATIC_DRAW);
	::glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vtc), vtc);
	::glBufferSubData(GL_ARRAY_BUFFER, sizeof(vtc), sizeof(tex), tex);

	GLuint program = initShader("vshader.glsl", "fshader.glsl");
	::glUseProgram(program);
	GLint vPosition = ::glGetAttribLocation(program, "vPosition");

	::glEnableVertexAttribArray(vPosition);
	::glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, 0);

	GLint vTexCoord = ::glGetAttribLocation(program, "vTexCoord");
	::glEnableVertexAttribArray( vTexCoord);
	::glVertexAttribPointer(vTexCoord, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid *) sizeof(vtc));
	::glProgramUniform1i(program, ::glGetUniformLocation(program, "texture"), 0);
}

void SWRenderer::createBuffers()
{
	_backBuffer = vector<float>(3 * _width->getValueI() * _height->getValueI());

	createOpenGLBuffer();
}

void SWRenderer::createOpenGLBuffer()
{
	::glActiveTexture(GL_TEXTURE0);
	::glBindTexture(GL_TEXTURE_2D, glScreenTex);
	::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, _width->getValueI(), _height->getValueI(), 0, GL_RGB, GL_FLOAT, NULL);
	::glViewport(0, 0, _width->getValueI(), _height->getValueI());
}

void SWRenderer::resize()
{
	createBuffers();
}

void SWRenderer::startScene()
{
	// clear old data
	::memset(_backBuffer.data(), 0, _backBuffer.size() * sizeof(int));
}

void SWRenderer::endScene()
{
	// swap buffers to render result
	::glActiveTexture(GL_TEXTURE0);
	::glBindTexture(GL_TEXTURE_2D, glScreenTex);
	::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _width->getValueI(), _height->getValueI(), GL_RGB, GL_FLOAT, _backBuffer.data());
	::glGenerateMipmap(GL_TEXTURE_2D);
	::glBindVertexArray(glScreenVtc);
	::glDrawArrays(GL_TRIANGLES, 0, 6);
	
	if (_useGlut->getValueB()) {
		::glutSwapBuffers();
	}
	else {
		::glFlush();
	}
}

void SWRenderer::setModelTransform(const Mat4& m)
{
	_modelTransform = m;
}

void SWRenderer::drawPixel(int x, int y, int argb)
{
	if (x < 0 || x >= _width->getValueI() || y < 0 || y >= _height->getValueI()) {
		return;
	}

	int r = (argb >> 16) & 0xFF;
	int g = (argb >> 8)  & 0xFF;
	int b = (argb >> 0)  & 0xFF;

	int width = _width->getValueI();

	_backBuffer[(x+y*width)*3 + 0] = (float) r / 255;
	_backBuffer[(x+y*width)*3 + 1] = (float) g / 255;;
	_backBuffer[(x+y*width)*3 + 2] = (float) b / 255;;
}

void SWRenderer::drawLine(const Vec3& v1, const Vec3& v2, int argb)
{
	// world and camera transform
	Vec4 v1h = project(transformToWorld(v1));
	Vec4 v2h = project(transformToWorld(v2));

	// projection and view-port transform
	Vec2 u1 = transformToView(v1h);
	Vec2 u2 = transformToView(v2h);

	// clip using projected x,y and original z
	if (clip(Vec3(u1, v1h.z)) && clip(Vec3(u2, v2h.z))) {
		return;
	}

	// round
	int x1 = (int) floor(u1.x + 0.5f);
	int y1 = (int) floor(u1.y + 0.5f);
	int x2 = (int) floor(u2.x + 0.5f);
	int y2 = (int) floor(u2.y + 0.5f);

	// draw		
	bresenham(x1, y1, x2, y2, argb); 
}

void SWRenderer::drawTriangle(const Vec3& v1, const Vec3& v2, const Vec3& v3)
{
	if (_drawType->getValueI() == FILL) {
		fillTriangle(v1, v2, v3);
	}
	else if (_drawType->getValueI() == WIREFRAME) {
		int argb = _wireframeColor->getValueI();
		drawLine(v1, v2, argb);
		drawLine(v1, v3, argb);
		drawLine(v2, v3, argb);
	}
}

void SWRenderer::setCamera(CameraPtr camera)
{
	_camera = camera;	
}

void SWRenderer::bresenham(int x1, int y1, int x2, int y2, int argb)
{
	if(x2 < x1) {
		bresenham(x2, y2, x1, y1, argb);
		return;
	}

	if (x2 - x1 > INFINITY) {
		return;
	}

	drawPixel(x1, y1, argb);
	drawPixel(x2, y2, argb);
	
	int dx = x2-x1;
	int dy = y2 -y1;

	int step = (dy < 0 ? -1 : 1);
	dy *= step;

	int d, de, dne;
	if(dx >= dy) { // -1<=m<=1
		d = 2*dy-dx;
		de = 2*dy;
		dne = 2*(dy-dx);

		for (int x = 0, y = 0; x < dx; ++x) {
			if (d < 0) {
				d += de;
			}
			else {
				d += dne;
				++y;
			}

			drawPixel(x1 + x, y1 + (y*step), argb);
		}
	}
	else { // m<-1 and m>1
		d=2*dx-dy;
		de=2*dx;
		dne=2*(dx-dy);

		for (int x = 0, y = 0; y < dy; ++y) {
			if(d < 0) {
				d += de;
			} else {
				d += dne;
				++x;
			}

			drawPixel(x1 + x, y1 + (y*step), argb);
		}
	}
}

void SWRenderer::fillTriangle(const Vec3& v1, const Vec3& v2, const Vec3& v3)
{
	Vec4 v1h = transformToWorld(v1);
	Vec4 v2h = transformToWorld(v2);
	Vec4 v3h = transformToWorld(v3);

	// back face cull
	if (cull(v1h, v2h, v3h)) {
		return;
	}

	v1h = project(v1h);
	v2h = project(v2h);
	v3h = project(v3h);

	Vec2 u1 = transformToView(v1h);
	Vec2 u2 = transformToView(v2h);
	Vec2 u3 = transformToView(v3h);

	// clip using projected x,y and original z
	if (clip(Vec3(u1, v1h.z)) && clip(Vec3(u2, v2h.z)) && clip(Vec3(u3, v3h.z))) {
		return;
	}

	// calculate bounds
	int xmin = (int) floor(min(v1h.x, min(v2.x, v3.x)));
	int xmax = (int) ceil(max(v1.x, max(v2.x, v3.x)));
	int ymin = (int) floor(min(v1.y, min(v2.y, v3.y)));
	int ymax = (int) ceil(max(v1.y, max(v2.y, v3.y)));

	// for each pixel in the bounds, check if it's inside the triangle
	for (int x = xmin; x <= xmax; ++x) {
		for (int y = ymin; y <= ymax; ++y) {
			// TODO: compute Barycentric Coordinates and ignore pixel outside the triangle
			shadePixel();
		}
	}
}

void SWRenderer::shadePixel()
{

}

Vec4 SWRenderer::transformToWorld(const Vec3& v)
{
	// apply model world transformation
	return _modelTransform * v;
}

Vec4 SWRenderer::project(const Vec4& vh)
{
	Mat4 cameraTransform = _camera->getTransform();
	return cameraTransform * vh;
}

Vec2 SWRenderer::transformToView(const Vec4& v)
{
	Mat4 cameraProjection = _camera->getProjection();

	// apply camera projection
	Vec4 vh = cameraProjection * v;

	// to 3D
	if (abs(vh.w) > EPSILON)
		vh /= vh.w;	

	// transform to view port
	float c1 = (float) _width->getValueI() / 2.0f;

	float d1 = (float) _height->getValueI() / 2.0f;

	return Vec2(_initialWidth/2.0f*vh.x+c1, _initialHeight/2.0f*vh.y+d1); // (u, v)
}

bool SWRenderer::clip(const Vec3& v)
{
	float cnear, cfar;
	_camera->getBounds(NULL, NULL, NULL, NULL, &cnear, &cfar);

	return _clip->getValueB() && (
		v.x < 0 || v.x > _width->getValueI() ||
		v.y < 0 || v.y > _height->getValueI() ||
		v.z < cnear || v.z > cfar
	);
}

bool SWRenderer::cull(const Vec4& v1, const Vec4& v2, const Vec4& v3)
{
	Vec4 normal = (v1-v2).cross(v2-v3).normalize();
	// TODO: check angle between camera direction and face normal
	//Vec3 cDirection = _camera->getDirection();
	//return normal.dot(cDirection) / (normal.length() * cDirection.length()) >= 0;
	return false;
}